package cn.thens.kdroid.core.log

import kotlin.math.min

class PrettyPrinter(private val printer: IPrinter) : IPrinter {
    val settings = PrettySettings()

    private val style: PrettyStyle get() = settings.style

    override fun print(message: String) {
        printIfNotEmpty(style.top)
        printHeader(settings.methodCount)
        printIfNotEmpty(style.divider)
        printIfNotEmpty(message)
        printIfNotEmpty(style.bottom)
    }

    private fun printIfNotEmpty(message: String) {
        if (message.isEmpty()) return
        if (message.isEmpty()) return
        val bytes = message.toByteArray()
        val length = bytes.size
        val pageSize = (length - 1) / CHUNK_SIZE + 1
        for (i in 0 until pageSize) {
            val offset = i * CHUNK_SIZE
            val count = min(length - offset, CHUNK_SIZE)
            printChunk(String(bytes, offset, count, Charsets.UTF_8))
        }
    }

    private fun printChunk(chunk: String) {
        val lines = chunk.split('\n').dropLastWhile { it.isEmpty() }.toTypedArray()
        for (line in lines) {
            printer.print(style.middle + line)
        }
    }

    private fun printHeader(methodCount: Int) {
        val trace = Thread.currentThread().stackTrace
        val stackOffset = getStackOffset(trace) + settings.methodOffset
        val headerLineCount = min(methodCount, trace.size - 1 - stackOffset)
        var offset = ""
        for (i in 0 until headerLineCount) {
            val stackIndex = i + stackOffset
            printer.print(settings.style.middle + offset + trace[stackIndex].prettyInfo())
            offset += "  "
        }
    }

    private fun StackTraceElement.prettyInfo(): String {
        val className = className.substring(className.lastIndexOf(".") + 1)
        val threadName = Thread.currentThread().name
//        val spaceLength = 100 - (className.length + methodName.length + fileName.length)
//        val spaces = if (spaceLength <= 0) "" else (0..spaceLength).joinToString("") { " " }
        return "$className.$methodName ($fileName:$lineNumber) on Thread: $threadName"
    }

    private fun getStackOffset(trace: Array<StackTraceElement>): Int {
        var isInOffsetClass = false
        for (i in 0 until trace.size) {
            val traceElement = trace[i]
            val cls = Class.forName(traceElement.className)
            if (PrettyIgnored::class.java.isAssignableFrom(cls)) {
                isInOffsetClass = true
            } else if (isInOffsetClass) {
                return i
            }
        }
        return 0
    }

    companion object {
        private const val CHUNK_SIZE = 4 * 1024
    }

}